package org.pkg.jstools;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.NativeArray;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.Scriptable;
import org.pkg.jstools.preferences.PreferenceConstants;

/**
 * @author praveen.kailas@gmail.com
 * 
 */
public class JSLint {

	private Context context = null;
	private Scriptable scope = null;

	/**
	 * 
	 */
	public JSLint() {

	}

	/**
	 * Invokes the JSLint validator on the file.
	 * 
	 * @param file
	 *            the file to validate.
	 */
	public void validate(IFile file) {
		if (file == null) {
			return;
		}
		if (!file.getFileExtension().equalsIgnoreCase("js")) {
			return;
		}

		initContext();

		String contents = "";
		String options = "/*jslint maxerr: 50 */";
		try {
			contents = getFileContents(file.getContents());
			
			options = getLintPreferences().toString().replaceAll("=", ": ")
					.replaceFirst("\\{", "/*jslint ").replaceAll("\\}", " */");
			
			contents = options + "\n\n" + contents;

			scope.put("contents", scope, contents);
			context.evaluateString(scope, "results = JSLINT(contents, null);", "JSLint", 1, null);
			
			Scriptable lint = (Scriptable) scope.get("JSLINT", scope);
			NativeArray errors = (NativeArray) lint.get("errors", null);
			clearMarkers(file);
			for (int i = 0; i < errors.getLength(); i++) {
				NativeObject error = (NativeObject) errors.get(i, null);
				if(error == null) continue;
				Double lineNo = ((Double) error.get("line", null)) - 2;
				Object reason = error.get("reason", null);
				IMarker marker = file.createMarker("org.eclipse.core.resources.problemmarker");
				marker.setAttribute(IMarker.LINE_NUMBER, lineNo.intValue());
				marker.setAttribute(IMarker.MESSAGE, reason);
				marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
				marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
				// Needed to delete own markers!
				marker.setAttribute("org.pkg.jstools.marker", true);
			}
			if (errors.getLength() == 0) {
				MessageDialog.openInformation(null, "JSLint", "No errors found.");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			scope = null;
			context = null;
		}
	}

	public void clearMarkers(IResource resource) {
		IMarker[] markers = null;
		try {
			markers = resource.findMarkers(
					"org.eclipse.core.resources.problemmarker", true,
					IResource.DEPTH_INFINITE);
		} catch (Exception e) {
			e.printStackTrace();
		}
		for (IMarker marker : markers) {
			try {
				if (marker.getAttribute("org.pkg.jstools.marker") != null
						&& marker.getAttribute("org.pkg.jstools.marker")
								.toString() == "true") {
					marker.delete();
				}
			} catch (CoreException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * @return
	 */
	private void initContext() {
		try {
			context = Context.enter();
			context.setLanguageVersion(Context.VERSION_1_6);
			scope = context.initStandardObjects();
			String jsLintCode = getFileContents(JSLint.class
					.getResourceAsStream("fulljslint.js"));
			context.evaluateString(scope, jsLintCode, "JSLint", 1, null);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private String getFileContents(InputStream stream) {
		StringBuffer contents = new StringBuffer("");
		try {
			byte[] readBytes = new byte[1024];
			int i = 0;
			while ((i = stream.read(readBytes)) > 0) {
				contents.append(new String(readBytes, 0, i));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return contents.toString();
	}

	private Map<String, Object> getLintPreferences() {
		Map<String, Object> map = new HashMap<String, Object>();

		IPreferenceStore prefs = JSToolsActivator.getDefault().getPreferenceStore();

		map.put(PreferenceConstants.JSLINT_ADSAFE, prefs.getBoolean(PreferenceConstants.JSLINT_ADSAFE));
		map.put(PreferenceConstants.JSLINT_BITWISE, prefs.getBoolean(PreferenceConstants.JSLINT_BITWISE));
		map.put(PreferenceConstants.JSLINT_BROWSER, prefs.getBoolean(PreferenceConstants.JSLINT_BROWSER));
		map.put(PreferenceConstants.JSLINT_CAP, prefs.getBoolean(PreferenceConstants.JSLINT_CAP));
		map.put(PreferenceConstants.JSLINT_CONFUSION, prefs.getBoolean(PreferenceConstants.JSLINT_CONFUSION));
		map.put(PreferenceConstants.JSLINT_CONTINUE, prefs.getBoolean(PreferenceConstants.JSLINT_CONTINUE));
		map.put(PreferenceConstants.JSLINT_CSS, prefs.getBoolean(PreferenceConstants.JSLINT_CSS));
		map.put(PreferenceConstants.JSLINT_DEBUG, prefs.getBoolean(PreferenceConstants.JSLINT_DEBUG));
		map.put(PreferenceConstants.JSLINT_DEVEL, prefs.getBoolean(PreferenceConstants.JSLINT_DEVEL));
		map.put(PreferenceConstants.JSLINT_EQEQ, prefs.getBoolean(PreferenceConstants.JSLINT_EQEQ));
		map.put(PreferenceConstants.JSLINT_ES5, prefs.getBoolean(PreferenceConstants.JSLINT_ES5));
		map.put(PreferenceConstants.JSLINT_EVIL, prefs.getBoolean(PreferenceConstants.JSLINT_EVIL));
		map.put(PreferenceConstants.JSLINT_FORIN, prefs.getBoolean(PreferenceConstants.JSLINT_FORIN));
		map.put(PreferenceConstants.JSLINT_FRAGMENT, prefs.getBoolean(PreferenceConstants.JSLINT_FRAGMENT));
		map.put(PreferenceConstants.JSLINT_NEWCAP, prefs.getBoolean(PreferenceConstants.JSLINT_NEWCAP));
		map.put(PreferenceConstants.JSLINT_NODE, prefs.getBoolean(PreferenceConstants.JSLINT_NODE));
		map.put(PreferenceConstants.JSLINT_NOMEN, prefs.getBoolean(PreferenceConstants.JSLINT_NOMEN));
		map.put(PreferenceConstants.JSLINT_ON, prefs.getBoolean(PreferenceConstants.JSLINT_ON));
		map.put(PreferenceConstants.JSLINT_PASSFAIL, prefs.getBoolean(PreferenceConstants.JSLINT_PASSFAIL));
		map.put(PreferenceConstants.JSLINT_PLUSPLUS, prefs.getBoolean(PreferenceConstants.JSLINT_PLUSPLUS));
		map.put(PreferenceConstants.JSLINT_REGEXP, prefs.getBoolean(PreferenceConstants.JSLINT_REGEXP));
		map.put(PreferenceConstants.JSLINT_RHINO, prefs.getBoolean(PreferenceConstants.JSLINT_RHINO));
		map.put(PreferenceConstants.JSLINT_SAFE, prefs.getBoolean(PreferenceConstants.JSLINT_SAFE));
		map.put(PreferenceConstants.JSLINT_SLOPPY, prefs.getBoolean(PreferenceConstants.JSLINT_SLOPPY));
		map.put(PreferenceConstants.JSLINT_SUB, prefs.getBoolean(PreferenceConstants.JSLINT_SUB));
		map.put(PreferenceConstants.JSLINT_UNDEF, prefs.getBoolean(PreferenceConstants.JSLINT_UNDEF));
		map.put(PreferenceConstants.JSLINT_UNPARAM, prefs.getBoolean(PreferenceConstants.JSLINT_UNPARAM));
		map.put(PreferenceConstants.JSLINT_VARS, prefs.getBoolean(PreferenceConstants.JSLINT_VARS));
		map.put(PreferenceConstants.JSLINT_WHITE, prefs.getBoolean(PreferenceConstants.JSLINT_WHITE));
		map.put(PreferenceConstants.JSLINT_WIDGET, prefs.getBoolean(PreferenceConstants.JSLINT_WIDGET));
		map.put(PreferenceConstants.JSLINT_WINDOWS, prefs.getBoolean(PreferenceConstants.JSLINT_WINDOWS));
		
		cleanPrefs(map);
		
		if (prefs.getInt(PreferenceConstants.JSLINT_INDENT) > 0) {
			map.put(PreferenceConstants.JSLINT_INDENT, prefs.getInt(PreferenceConstants.JSLINT_INDENT));
		}
		
		if (prefs.getInt(PreferenceConstants.JSLINT_LINELEN) > 0) {
			map.put(PreferenceConstants.JSLINT_LINELEN, prefs.getInt(PreferenceConstants.JSLINT_LINELEN));
		}
		
		if (prefs.getInt(PreferenceConstants.JSLINT_ERRS) > 0) {
			map.put(PreferenceConstants.JSLINT_ERRS, prefs.getInt(PreferenceConstants.JSLINT_ERRS));
		}
		
		if (prefs.getString(PreferenceConstants.JSLINT_PREDEF).length() > 0) {
			//map.put(PreferenceConstants.JSLINT_PREDEF, "[" + prefs.getString(PreferenceConstants.JSLINT_PREDEF) + "]");
		}
		
		return map;
	}

	/**
	 * @param map
	 */
	private void cleanPrefs(Map<String, Object> map) {
		Set<String> keys = map.keySet();
		Map<String, Object> nMap = new HashMap<String, Object>();
		for(String key : keys) {
			nMap.put(key, map.get(key).toString() != "false");
		}
		map.clear();
		map.putAll(nMap);
	}
}
